<?php
namespace Tlf;
class User {
use \Tlf\User\Permissions;
////////
// Non DB fields
////////
/** user model from sentinel */
public $pdo;
/** The key in $_COOKIE['key'] that the login code is stored in */
static public $cookie_name = 'taeluf_login';
/** Number of seconds before a new cookie expires */
public $cookie_expiry = 15552000; //180 days: 60*60*24*180;
public $registration_expiry = 432000; //5 days: 60*60*24*5;
// public $password_expiry = 946080000; //30 years: 60*60*24*365*30;
public $password_reset_expiry = 86400; // 1 day: 60*60*24;
public $is_logged_in = false;
////////
// database fields
////////
/** email address of user
* @todo remove hacky approach to syncing this class's email & the model's email
*/
public string $email;
public int $id = -1;
public bool $is_active = false;
/**
* Array of query strings identifiable by key. Generated by LilSql (of LilDb package)
*/
public array $queries = [];
public function __construct($pdo){
$this->pdo = $pdo;
$this->queries = unserialize(file_get_contents(__DIR__.'/../db/serialized.txt'));
}
/** Set values from a database row
*/
public function from_row(array $row){
$this->email = $row['email'];
$this->id = $row['id'];
$this->is_active = $row['is_active'];
}
////////
//// user status (registered, active, logged in)
////////
/**
* returns true/false whether logged in or not.
*/
public function is_logged_in():bool {
return $this->is_logged_in;
}
/**
* Set the login cookie code via `setcookie()` & also set it to `$_COOKIE`
* @return return value from `setcookie()` (true if it sent. False if it's too late to send headers)
*/
public function set_login_cookie($code){
$_COOKIE[static::$cookie_name] = $code;
return setcookie(static::$cookie_name, $code, time()+$this->cookie_expiry, '/', '', true,true);
}
/**
* Change `$user->is_logged_in` to false. `setcookie()` to delete the cookie. de-activate the cookie key in the database
*
* @return the old cookie code which SHOULD be disabled now
*/
public function logout(){
$code = $_COOKIE[static::$cookie_name] ?? null;
setcookie(static::$cookie_name, '', 1);
$stmt=$this->pdo->prepare($this->queries['user.logout']);
$stmt->execute(['code'=>$_COOKIE[static::$cookie_name], 'user_id'=>$this->id]);
unset($_COOKIE[static::$cookie_name]);
return $code;
}
/**
* Get a login cookie after validating the password. You must call $this->set_login_cookie($code) to actually send the cookie to the browser. Or the `setcookie()` function if you wish to customize it
*
* @return string login cookie on success, false on failure
*/
public function password_login(string $password) {
if (!$this->is_active
||$password===null
||$password===''
||$password==='0'
){
return false;
}
$stmt = $this->pdo->prepare($this->queries['user.get_password']);
$stmt->execute(['email'=>$this->email]);
if ($stmt==false){
return false;
}
$rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
if (count($rows)!==1)return false;
if (!password_verify($password, $rows[0]['password']))return false;
$code = $this->new_code('login_cookie');
$this->is_logged_in = true;
return $code;
}
/** Activate a user
* @param $code a code generated by new_activation_code()
* @return true if the activation succeeds or if the code is already active. False otherwise
*/
public function activate(string $code):bool{
$stmt = $this->pdo->prepare($this->queries['user.activate']);
$stmt->execute([
'code'=>$code,
'user_id'=>$this->id
]);
if ($stmt->rowCount()==0)return false;
$stmt->nextRowset();
$type = $stmt->fetch()[0];
if ($type=='registration'){
$this->is_active = true;
}
return true;
}
/**
* Generate a new cryptographically secure code (& insert into database)
*
* @param $type 'registration' or 'password_reset' or 'login_cookie'
* @return the code
*/
public function new_code(string $type): string{
// i need different expiry for activation codes, reset password codes, and login_cookie codes
$code = bin2hex(random_bytes(100));
if ($type=='registration')$expiry = $this->registration_expiry;
else if ($type=='login_cookie')$expiry = $this->cookie_expiry;
else if ($type=='password_reset')$expiry = $this->password_reset_expiry;
$data = [
'code'=>$code,
'type'=>$type,
'user_id'=>$this->id,
'is_active' => 0,
'expiry'=>$expiry,
];
$activated_at = 'NULL';
if ($type=='login_cookie'){
$data['is_active'] = 1;
$activated_at = 'NOW()';
}
$stmt = $this->pdo->prepare(
sprintf($this->queries['user.new_code'], $activated_at)
);
$stmt->execute($data);
return $code;
}
/**
* @return int id on success, boolean false on failure
*/
public function register(string $password){
$stmt = $this->pdo->prepare($this->queries['user.register']);
$stmt->execute(
[
'email'=>$this->email,
'password'=>password_hash($password,PASSWORD_DEFAULT),
]
);
$this->id = $this->pdo->lastInsertId();
// var_dump($this->id);
// exit;
return $this->id;
}
/**
* Set a new password on the user. Executes a pdo/mysql UPDATE query.
*
* @param $password a string password, as the user typed
* @param $code a code received from `$this->new_code("password_reset"): string`
*
* @return true if password changed. False otherwise
*/
public function new_password(string $password, string $code): bool{
$hash = password_hash($password,PASSWORD_DEFAULT);
//run a query that updates the password hash only if the code is valid
$stmt = $this->pdo->prepare($this->queries['user.new_password']);
$stmt->execute([
'password_hash'=>$hash,
'code'=>$code,
'user_id'=>$this->id
]);
// echo $code;
// print_r($this->pdo->errorInfo());
//
// var_dump($stmt->rowCount());
if ($stmt->rowCount()!==2)return false;
return true;
}
/**
* Checks if a user has registered for the site
* @returns true if user exists in database, regardless of activation status
*/
public function is_registered():bool{
if ($this->id<0)return false;
//if there IS a model, this means the user is in the database
return true;
}
/**
* Check if a user is active (clicked the link in the confirmation email)
* @return true if user has clicked the link in their activation email. False otherwise
*/
public function is_active():bool{
//maybe should load activation status in initial query for the user.
if ($this->is_active)return true;
return false;
}
public function security_logs($limit = 100){
$stmt = $this->pdo->prepare(sprintf($this->queries['user.get_logs'], $limit));
$stmt->execute(
['email'=>$this->email]
);
$rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
return $rows;
}
}